# auditd_core.bro
# Scott Campbell
#
# 
# Every login and related activities is associated with login session id (ses)
#   and the {pid} pair.  This collection of stuff identifies the key which
#   is actually the table used to hold multi action/record data.
#
# The ses id is monotomicly incrementing, so the odds of collision between many
#   systems is reasonably high.  Because of this the node identity is appended to 
#   ses and pid values since the internal systems should remove duplicate values.
#
module AUDITD_CORE;

export {

	# AUDITD_CORE log stream identifier
	redef enum Log::ID += { LOG };

	# auditd session record: current model supports one line per action, each filled in
	#  with any number of records.
	#
	type Info: record {
		# basic indexing information
		ts: time &log;				#
		id: string &log &default="NULL";	# identifier provided by key in getid()
		ses: int &log &default=-1;		# numeric session id or 'unset'
		pid: int &log &default=-1;		# curent pid
		node: string &log &default="NULL";	# what host is this happening on
		whoami: string &log &default="NULL";	# index that this record is stored under

		# class of info
		action: string &log &default="NULL";	# class of action (ex: 'SYSCALL_OBJ')
		key: string &log &default="NULL";	# subtype of class (ex: 'SYS_NET')

		# what happened
		syscall: string &log &default="NULL";	# syscall name
		comm: string &log &default="NULL";	# name as appears in 'ps'
		exe: string &log &default="NULL";	# full exe name + path

		# info about what happened
		msg: string &log &default="NULL";
		s_type: string &log &default="NULL";	# name or file type socket
		s_host: string &log &default="NULL";	# *where* the socket type is pointing
		s_serv: string &log &default="NULL";	# service it is pointing to
		path_name: string &log &default="NULL";	# gen 1x per path element passed to syscall
		cwd: string &log &default="NULL";	# current working direct at time of syscall
		a0: string &log &default="NULL"; 	# argument0 to syscall
		a1: string &log &default="NULL"; 	# ..
		a2: string &log &default="NULL"; 	# ..
		arg_t: string &log &default="NULL";	# for exec, *total* set of args
		ppid: int &log &default=-1; 		# parent pid
		tty: string &log &default="NULL"; 	# tty type or NO_TTY
		terminal: string &log &default="NULL"; 	# terminal data or NO_TERM
		success: string &log &default="NULL";	# if syscall succedded or not 
		ext: string &log &default="NULL";	# exit code for call

		# identity information
		# note that these are string values since the data normalizer will try
		#   to return the translated numeric value.
		#
		auid: string &log &default=-1; 		# audit id, immutable even if id changes
		uid: string &log &default=-1; 		# user id
		gid: string &log &default=-1; 		# group id
		euid: string &log &default=-1; 		# effective user id
		egid: string &log &default=-1; 		# effective group id
		suid: string &log &default=-1; 		# set user id
		sgid: string &log &default=-1; 		# set group id
		fsuid: string &log &default=-1; 	# file system user id
		fsgid: string &log &default=-1; 	# file system group id
		};


	const zero_int: int = 0;	
	# something to hold the pid data
	type pid_pair: record {
		pid: int &default=zero_int;
		key: string &default="NULL";
		};

	type ses_data: record {
		ses: int &default=zero_int;
		key: string &default="NULL";
		};

	# Tables to hold key mappings:
	#    ses/pid gives you the session key, which holds the auditd data
	#
	# look up the p/pid and key from the ses
	global sesPidMap: table[string] of pid_pair;

	# look up the ses and key from the pid
	global pidSesMap: table[string] of ses_data;

	# main state table: driven by *key*
	global auditdState: table[string] of Info;

	# exported functions
	global getid: function(index: string, pid: int, ses: int, node: string) : string;
	global getrecord: function(index: string, pid: int, ses: int, node: string) : Info;
	global last_record: function(index: string): count;
	global update_value: function(i: Info);

	}

### ----- # ----- ###
#      Functions
### ----- # ----- ###

function getid(index: string, pid: int, ses: int, node: string) : string
{
	# This function returnes the index value for the auditd action 
	#   object.  The separation of index lookup and data is intentional
	#   to make this a bit more modular and less complex.
	#

	# This function should never return this value.
	local ret = "NULL";

	# take index value (a:b) and split it up
	local i = split(index, /:/);
	local i_major = to_count(i[1]);
	local i_minor = to_count(i[2]);

	# generate per node key lookup values
	local sesnode = fmt("%s%s", ses, node);
	local pidnode = fmt("%s%s", pid, node);

	# first check and see if the ses is in the live pool since
	#  this is the most likely candidate
	if ( sesnode in sesPidMap ) {
		local tpid_pair = sesPidMap[sesnode];
		ret = tpid_pair$key;
		}
	# if the pid + node is in the pidSesMap then we use it
	else if ( pidnode in pidSesMap ) {
		local tses_data = pidSesMap[pidnode];
		ret = tses_data$key;
		} 
	# finally we conclude that this is a new entry and fill
	#   in the map values
	else {
		local key = unique_id("");
		ret = key;

		local tpp: pid_pair;
		local tsd: ses_data;

		tpp$pid = pid;
		tpp$key = key;

		tsd$ses = ses;
		tsd$key = key;

		# since the ses is not always set or correct (ex it can be a string)
		#   we do a test to make sure that the value is legitimate.
		# to_count will return 0 if ses has an invalid format  
		#
		if ( ses != 0 )
			sesPidMap[sesnode] = tpp;

		# always fill this in since we need *somethin* here
		pidSesMap[pidnode] = tsd;		
		}

	return ret;
} # function getid end

function get_record(index: string, pid: int, ses: int, node: string) : Info
{
	local key = getid(index,pid,ses,node);
	local t_Info: Info;

	# If the key is been registered use it, else
	#  use t_Info.
	if ( key in auditdState ) {
		t_Info = auditdState[key];
		}
	else {
		# add the key instance
		t_Info$whoami = key;
		t_Info$node = node;
		auditdState[key] = t_Info;
		}

	return t_Info;

} # end get_record

function last_record(index: string): count
{
	# test the index field to see if this is the last record in a series
	#  3:2:2 means index:total_records:record_index
	# so in this case the result would be true
	#
	local ret = 0;
	local index_split = split(index, /:/);

	if ( index_split[2] == index_split[3] )
		ret = 1;
	return ret;
}

function update_value(i: Info)
{
	# update the record value
	if ( i$whoami in auditdState ) {

		auditdState[i$whoami] = i;
		}
	else {
		print fmt("UPDATE ERROR for index %s", i$whoami);
		}
}

### ----- # ----- ###
#      Events
### ----- # ----- ###
event auditd_execve(index: string, action: string, ts: time, node: string, ses: int, pid: int, argc: int, argument: string)
	{
	# look up the related record
	local t_Info = get_record(index,pid,ses,node);

	# for now just update the field values
	# only update the action for some types
	t_Info$action = action;
	t_Info$ts = ts;
	t_Info$ses = ses;
	t_Info$pid = pid;
	t_Info$arg_t = argument;

	update_value(t_Info);

	# if the last record, print it
	if ( last_record(index) == 1 ) 
		Log::write(LOG, t_Info);

	}


event auditd_generic(index: string, action: string, ts: time, node: string, ses: int, pid: int, auid: string, comm: string, exe: string, a0: string, a1: string, a2: string, uid: string, gid: string, euid: string, egid: string, fsuid: string, fsgid: string, suid: string, sgid: string, ppid: int, tty: string, terminal: string, success: string, ext: string)
	{
	# look up the related record
	local t_Info = get_record(index,pid,ses,node);

	# for now just update the field values
	# only update the action for some types
	t_Info$action = action;
	t_Info$ts = ts;
	t_Info$ses = ses;
	t_Info$pid = pid;
	#
	t_Info$comm = comm;
	t_Info$exe = exe;
	t_Info$a0 = a0;
	t_Info$a1 = a1;
	t_Info$a2 = a2;
	t_Info$ppid = ppid;
	t_Info$tty = tty;
	t_Info$terminal = terminal;
	t_Info$success = success;
	t_Info$ext = ext;
	# identification
	t_Info$uid = uid;
	t_Info$gid = gid;
	t_Info$auid = auid;
	t_Info$euid = euid;
	t_Info$egid = egid;
	t_Info$suid = suid;
	t_Info$sgid = sgid;
	t_Info$fsuid = fsuid;
	t_Info$fsgid = fsgid;

	update_value(t_Info);

	# if the last record, print it
	if ( last_record(index) == 1 )
		Log::write(LOG, t_Info);

	}

event auditd_place(index: string, action: string, ts: time, node: string, ses: int, pid: int, cwd: string, path_name: string, inode: int, mode: int, ouid: string, ogid: string)
	{
	# ouid/ogid: Refer to the UID and GID of the inode itself. 
	#
	# look up the related record
	local t_Info = get_record(index,pid,ses,node);

	# for now just update the field values
	#  that are related to the current record
	#
	t_Info$cwd = cwd;
	t_Info$path_name = path_name;

	# quick test to look at diff between oxid and xid
	if ( ouid != t_Info$uid ) 	
		print fmt("OUID ERROR ouid: %s uid: %s", ouid, t_Info$uid);

	if ( ogid != t_Info$gid ) 	
		print fmt("OGID ERROR ogid: %s gid: %s", ogid, t_Info$gid);

	update_value(t_Info);

	# if the last record, print it
	if ( last_record(index) == 1 ) 
		Log::write(LOG, t_Info);

	}

event auditd_saddr(index: string, action: string, ts: time, node: string, ses: int, pid: int, saddr: string)
	{

	# most of the work here will be in decoding the saddr structure
	#
	# common types:
	# 	inet host 1.2.3.4 serv:123
	# 	local /dev/filename
	# 	netlink /dev/log
	#
	# will be broken out into the followign structures
	#
	# 	type : {inet host|local|netlink}
	# 	host : {file|device|ip} identifies where
	# 	serv : {port} (optional) identifies what
	#
	
	local t_Info = get_record(index,pid,ses,node);

	# decode the saddr structure
	local t_saddr = unescape_URI(saddr);
	local split_saddr = split(t_saddr, / / );

	local stype = split_saddr[1];
	local host = split_saddr[2];

	if ( |split_saddr| > 2 ) {
		local serv = split_saddr[3];
		local t_serv = split( serv, /:/ );
		}

	local t_host = split( host, /:/ );

	# make decisions based on field 1
	if ( stype == "inet" ) {

		t_Info$s_type = stype;
		t_Info$s_host = t_host[2];
		t_Info$s_serv = t_serv[2];

		}
	else if ( stype == "local" ) {
		
		t_Info$s_type = stype;
		t_Info$s_host = host;

		} 
	else if ( stype == "netlink" ) {

		t_Info$s_type = stype;
		t_Info$s_host = t_host[2];
		
		}

	update_value(t_Info);

	# if the last record, print it
	if ( last_record(index) == 1 )
		Log::write(LOG, t_Info);
	}


event auditd_syscall(index: string, action: string, ts: time, node: string, ses: int, pid: int, auid: string, syscall: string, key: string, comm: string, exe: string, a0: string, a1: string, a2: string, uid: string, gid: string, euid: string, egid: string, fsuid: string, fsgid: string, suid: string, sgid: string, ppid: int, tty: string, success: string, ext: string)
	{
	# look up the related record
	local t_Info = get_record(index,pid,ses,node);

	# for now just update the field values
	# only update the action for some types
	t_Info$action = action;
	t_Info$ts = ts;
	t_Info$ses = ses;
	t_Info$pid = pid;
	#
	t_Info$syscall = syscall;
	t_Info$key = key;
	t_Info$comm = comm;
	t_Info$exe = exe;
	t_Info$a0 = a0;
	t_Info$a1 = a1;
	t_Info$a2 = a2;
	t_Info$ppid = ppid;
	t_Info$tty = tty;
	t_Info$success = success;
	t_Info$ext = ext;
	# identification
	t_Info$uid = uid;
	t_Info$gid = gid;
	t_Info$auid = auid;
	t_Info$euid = euid;
	t_Info$egid = egid;
	t_Info$suid = suid;
	t_Info$sgid = sgid;
	t_Info$fsuid = fsuid;
	t_Info$fsgid = fsgid;

	update_value(t_Info);

	# if the last record, print it
	if ( last_record(index) == 1 )
		Log::write(LOG, t_Info);
	}

event auditd_user(index: string, action: string, ts: time, node: string, ses: int, pid: int, auid: string, euid: string, egid: string, fsuid: string, fsgid: string, suid: string, sgid: string, uid: string, gid: string, exe: string, terminal: string, success: string, ext: string, msg: string)
	{
	# look up the related record
	local t_Info = get_record(index,pid,ses,node);

	# for now just update the field values
	# only update the action for some types
	t_Info$action = action;
	t_Info$ts = ts;
	t_Info$ses = ses;
	t_Info$pid = pid;
	#
	t_Info$msg = msg;
	t_Info$exe = exe;
	t_Info$terminal = terminal;
	t_Info$success = success;
	t_Info$ext = ext;
	# identification
	t_Info$uid = uid;
	t_Info$gid = gid;
	t_Info$auid = auid;
	t_Info$euid = euid;
	t_Info$egid = egid;
	t_Info$suid = suid;
	t_Info$sgid = sgid;
	t_Info$fsuid = fsuid;
	t_Info$fsgid = fsgid;

	update_value(t_Info);

	# if the last record, print it
	if ( last_record(index) == 1 )
		Log::write(LOG, t_Info);
	}
	

event bro_init() &priority = 5
{
	  Log::create_stream(AUDITD_CORE::LOG, [$columns=Info]);
}
